using System;
using System.ComponentModel;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using NHibernate;
using NHibernate.Mapping;
using NHibernate.Type;
using Ornament.NHibernates;

namespace Ornament.Web.Mvc
{
    public class NHModelBinder : DefaultModelBinder, IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            SessionManager.Instance.CleanUp();
        }

        protected virtual ISession CurrentSession
        {
            get
            {
                SessionManager.Instance.IniSession();
                return SessionManager.Instance.CurrentSession;
            }
        }


        #endregion

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext,
                                              Type modelType)
        {
            if (!IsPersistentType(modelType))
            {
                return base.CreateModel(controllerContext, bindingContext, modelType);
            }
            HttpRequestBase request = controllerContext.RequestContext.HttpContext.Request;
            object result = GeModelFromNH(modelType, CurrentSession, request);
            if (result == null)
            {
                return CreateModelByDefaultConstructor(modelType);
            }
            return result;
        }

        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext,
                                            PropertyDescriptor propertyDescriptor, object value)
        {
            ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];

            if (!IsPersistentType(propertyMetadata.ModelType))
            {
                base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
            }
            else
            {
                // property is the persistent object,
                //var s = value = "";
                var idOfProperty = controllerContext.RequestContext.HttpContext.Request[propertyDescriptor.Name];
                PersistentClass mappingInfo = SessionManager.Instance.Config.NHConfiguration.GetClassMapping(propertyDescriptor.PropertyType);
                var id = ConvertIdFromStringValue(idOfProperty, mappingInfo.Identifier.Type);
                value = CurrentSession.Load(propertyDescriptor.PropertyType, id);
                base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
            }
        }

        private static bool IsPersistentType(Type modelType)
        {
            PersistentClass res = SessionManager.Instance.Config.NHConfiguration.GetClassMapping(modelType);
            return res != null;
        }

        private static object CreateModelByDefaultConstructor(Type modelType)
        {
            ConstructorInfo obj =
                modelType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance,
                                         null,
                                         null, null);
            return obj;
        }

        protected virtual Object GeModelFromNH(Type modelType, ISession session, HttpRequestBase request)
        {
            object idValue = GetIdValue(modelType, request);
            return session.Load(modelType, idValue);
        }

        private static object GetIdValue(Type modelType, HttpRequestBase request)
        {
            PersistentClass mappingInfo = SessionManager.Instance.Config.NHConfiguration.GetClassMapping(modelType);
            string idValue = request[mappingInfo.IdentifierProperty.Name];
            if (idValue == null)
                return null;
            return ConvertIdFromStringValue(idValue, mappingInfo.Identifier.Type);
        }

        private static object ConvertIdFromStringValue(string id, IType type)
        {
            var idType = type as NullableType;
            if (idType == null)
                throw new OrnamentException("Resource's Id only support mapping from  NullableType in nhibernate.");
            return idType.FromStringValue(id);
        }
    }
}